package com.ffmpeg.player;

import java.lang.ref.WeakReference;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Surface;
import com.byd.model.MyThreadPool;
import com.byd.utils.Lg;

public class FFmpegPlayer {
	private static FFmpegPlayer sFFmpegPlayer;
	private OnFFmpegPlayerListener mListener;
	private int mState = State.STOPED;
	private PlayerWorker mPlayerThread;
	private EventHandler mEventHandler;

	public static class State {
		public static final int STOPED = 0;
		public static final int PREPARING = 1;
		public static final int PLAYING = 2;
		public static final int PAUSING = 3;
	}

	private FFmpegPlayer() {
		mEventHandler = new EventHandler();
	}

	public synchronized static FFmpegPlayer getInstance() {
		if(sFFmpegPlayer == null) {
			sFFmpegPlayer = new FFmpegPlayer();
		}
		return sFFmpegPlayer;
	}

	public void play(String input) {
		play(input, null, null);
	}

	public void play(String input, Surface surface) {
		play(input, null, surface);
	}

	public void play(String input, String output) {
		play(input, output, null);
	}

	public void play(String input, String output, Surface surface) {
		if(mPlayerThread != null) {
			mPlayerThread.valid = false;
		}
		mPlayerThread = new PlayerWorker(input, output, surface);
		setState(input, output, State.PREPARING);
		MyThreadPool.postNewThread("ffmpegPlayer", mPlayerThread);
	}

	public void pause() {
		if(isPlaying()) {
			setState(mPlayerThread.input, mPlayerThread.output, State.PAUSING);
			Npause();
		}
		else {
			Lg.w("can't pause, error state: " + mState);
		}
	}

	public void start() {
		if(isPausing()) {
			setState(mPlayerThread.input, mPlayerThread.output, State.PLAYING);
			Nstart();
		}
		else {
			Lg.w("can't start, error state: " + mState);
		}
	}

	public void stop() {
		if(mPlayerThread != null) {
			setState(mPlayerThread.input, mPlayerThread.output, State.STOPED);
		}
		else {
			setState(null, null, State.STOPED);
		}
		Nstop();
	}

	public void fastForward(int mulriple) {
		if(isPlaying() || isPausing()) {
			NfastForward(mulriple);
		}
		else {
			Lg.w("can't fastForward, error state: " + mState);
		}
	}

	public void fastBackward(int mulriple) {
		if(isPlaying() || isPausing()) {
			NfastBackward(mulriple);
		}
		else {
			Lg.w("can't fastBackward, error state: " + mState);
		}
	}

	public void seekTo(int position) {
		if(isPreparing()) {
			mPlayerThread.seekTo(position);
		}
		else if(isPlaying() || isPausing()) {
			NseekTo(position);
		}
		else {
			Lg.w("can't seekTo " + position + ", error state: " + mState);
		}
	}
	
	public void setPositionCallback(boolean callback) {
		NsetPositionCallback(callback);
	}

	public void setOnFFmpegPlayerListener(OnFFmpegPlayerListener listener) {
		mListener = listener;
	}

	public boolean isPausing() {
		return mPlayerThread != null && mState == State.PAUSING;
	}

	public boolean isPreparing() {
		return mPlayerThread != null && mState == State.PREPARING;
	}

	public boolean isPlaying() {
		return mPlayerThread != null && mState == State.PLAYING;
	}

	public boolean isStop() {
		return mPlayerThread == null || mState == State.STOPED;
	}

	private void setState(String input, String output, int state) {
		if(mState != state) {
			mState = state;
			sendEvent(EventHandler.EVENT_STATE, input, output, state);
		}
	}

	private class PlayerWorker implements Runnable {
		private String input;
		private String output;
		private Surface surface;
		private int position;
		private boolean valid;

		private PlayerWorker(String input, String output, Surface surface) {
			this.input = input;
			this.output = output;
			this.surface = surface;
			valid = true;
			NregiestCallback(new WeakReference<PlayerWorker>(this));
		}

		@Override
		public void run() {
			int waitTime = 0;
			while (valid && Nready()) {
				Nstop();
				sleep(50);
				waitTime += 50;
				if(valid && waitTime >= 5000 && (waitTime % 1000) == 0) {
					Lg.e("wait ffmpeg ready for " + waitTime + "ms");
				}
			}
			if(valid) {
				setState(input, output, State.PLAYING);
			}
			int result = 0;
			if(valid) {
				result = Nplay(input, output, surface, position);
			}
			if(valid) {
				if(result != 0 && mListener != null) {
					sendEvent(EventHandler.EVENT_ERROR, input, output, result);
				}
				setState(input, output, State.STOPED);
			}
		}

		private void sleep(long time) {
			try {
				Thread.sleep(time);
			}
			catch (InterruptedException e) {
				Lg.printStackTrace(e);
			}
		}

		private void seekTo(int pos) {
			this.position = pos;
		}
	}

	private void sendEvent(int event, String input, String output, int value) {
		sendEvent(event, input, output, value, 0);
	}

	private void sendEvent(int event, String input, String output, int value1, int value2) {
		Bundle bundle = new Bundle();
		bundle.putString("input", input);
		bundle.putString("output", output);
		bundle.putInt("value1", value1);
		bundle.putInt("value2", value2);
		mEventHandler.obtainMessage(event, bundle).sendToTarget();
	}

	private class EventHandler extends Handler {
		static final int EVENT_PREPARED = 0;
		static final int EVENT_STATE = 1;
		static final int EVENT_PROGRESS = 2;
		static final int EVENT_ERROR = 3;

		@Override
		public void handleMessage(Message msg) {
			if(mListener != null) {
				Bundle bundle = (Bundle) msg.obj;
				String input = bundle.getString("input");
				String output = bundle.getString("output");
				int value1 = bundle.getInt("value1");
				int value2 = bundle.getInt("value2");
				switch (msg.what) {
				case EVENT_PREPARED:
					Lg.wtf("EVENT_PREPARED: [" + value1 + ", " + value2 + "]");
					break;
				case EVENT_STATE:
					mListener.onFFmpegStateChanged(input, output, value1);
					break;
				case EVENT_PROGRESS:
					mListener.onFFmpegProgressChanged(input, output, value1, value2);
					break;
				case EVENT_ERROR:
					mListener.onFFmpegError(input, output, value1);
					break;
				default:
					break;
				}
			}
		}
	}
	
	public interface OnFFmpegPlayerListener {
		public void onFFmpegStateChanged(String input, String output, int state);

		public void onFFmpegProgressChanged(String input, String output, int duration, int position);

		public void onFFmpegError(String input, String output, int errorCode);
	}

	private static void postEventFromNative(Object workerWeakRef, int event, int value1, int value2) {
		if(sFFmpegPlayer != null && workerWeakRef != null && workerWeakRef instanceof WeakReference<?>) {
			PlayerWorker worker = (PlayerWorker) ((WeakReference<?>) workerWeakRef).get();
			if(worker.valid) {
				sFFmpegPlayer.sendEvent(event, worker.input, worker.output, value1, value2);
			}
		}
	}
	
	public native int NregiestCallback(Object weakRef);

	public native int Nplay(String input, String output, Object surface, int position);

	public native boolean Npause();

	public native boolean Nstart();

	public native boolean Nstop();

	public native boolean NfastForward(int mulriple);

	public native boolean NfastBackward(int mulriple);

	public native boolean NseekTo(int position);
	
	public native boolean NsetPositionCallback(boolean callback);
	
	public native int NgetPosition();
	
	public native int NgetDuration();
	
	public native int NgetState();
	
	public native int NgetMulriple();
	
	public native int NgetWidth();
	
	public native int NgetHeight();

	public native boolean Nready();

	static {
		System.loadLibrary("avutil-54");
		System.loadLibrary("swresample-1");
		System.loadLibrary("avcodec-56");
		System.loadLibrary("avformat-56");
		System.loadLibrary("swscale-3");
		System.loadLibrary("postproc-53");
		System.loadLibrary("avfilter-5");
		System.loadLibrary("avdevice-56");
		System.loadLibrary("bydstreamer");
	}
}